//
//  NSString+WCExtensions.m
//  WabbitCode
//
//  Created by William Towe on 11/10/08.
//  Copyright 2008 Revolution Software. All rights reserved.
//

#import "NSString+WCExtensions.h"


#define kWCStringToNumberErrorDomain @"org.revsoft.wabbitcode.stringtonumber"

#define kWCInvalidBaseTenDigit 1001
#define kWCInvalidBaseSixteenDigit 1002
#define kWCInvalidBaseTwoDigit 1003

static inline NSInteger characterToBaseTenValue (unichar c);
static inline NSInteger characterToBaseSixteenValue (unichar c);
static inline NSInteger characterToBaseTwoValue (unichar c);

@implementation NSString (WCExtensions)
- (NSRange)wholeRange; {
	return NSMakeRange(0, [self length]);
}

- (BOOL)stringToNumber:(id *)value currentValue:(id)currentValue error:(NSError **)error {
	
	
	NSUInteger index, exp = 0, result = 0;
	NSInteger val = 0, stop = 0, start = [self length]-1;
	WCNumberBase base = [self baseForString:&stop start:&start];
	
	switch (base) {
		case WCBaseTen:
			for (index=start; index != stop; index--, exp++) {
				unichar c = [self characterAtIndex:index];
				val = characterToBaseTenValue(c);
				
				if (val == -1) {
					if (error != NULL) {
						NSString *desc = [NSString stringWithFormat:NSLocalizedString(@"'%c' is not a valid base 10 digit.", @"'%c' is not a valid base 10 digit."), c];
						NSString *recov = NSLocalizedString(@"You may only use numeric digits (0-9) when entering a base ten number.", @"You may only use numeric digits (0-9) when entering a base ten number.");
						NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:desc, NSLocalizedDescriptionKey, recov, NSLocalizedRecoverySuggestionErrorKey, nil];
						*error = [NSError errorWithDomain:kWCStringToNumberErrorDomain code:kWCInvalidBaseTenDigit userInfo:dict];
						return NO;
					}
					return NO;
				}
				
				result += val * pow(10, exp);
			}
			
			if (value != NULL) {
				if (result == 0) {
					result = [currentValue unsignedIntegerValue];
				}
				
				*value = [NSNumber numberWithUnsignedInteger:result];
				return YES;
			}
			return NO;
		case WCBaseSixteen:
			for (index=start; index != stop; index--, exp++) {
				unichar c = [self characterAtIndex:index];
				NSInteger value = characterToBaseSixteenValue(c);
				
				if (value == -1) {
					if (error != NULL) {
						NSString *desc = [NSString stringWithFormat:NSLocalizedString(@"'%c' is not a valid base 16 digit.", @"'%c' is not a valid base 16 digit."), c];
						NSString *recov = NSLocalizedString(@"You may only use numeric digits (0-9) or the letter A-F (upper or lowercase) when entering a base sixteen number.", @"You may only use numeric digits (0-9) or the letter A-F (upper or lowercase) when entering a base sixteen number.");
						NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:desc, NSLocalizedDescriptionKey, recov, NSLocalizedRecoverySuggestionErrorKey, nil];
						*error = [NSError errorWithDomain:kWCStringToNumberErrorDomain code:kWCInvalidBaseSixteenDigit userInfo:dict];
						return NO;
					}
					return NO;
				}
				
				result += value * pow(16, exp);
			}
			
			if (value != NULL) {
				if (result == 0) {
					result = [currentValue unsignedIntegerValue];
				}
				
				*value = [NSNumber numberWithUnsignedInteger:result];
				return YES;
			}
			return NO;
		case WCBaseTwo:
			for (index=start; index != stop; index--, exp++) {
				unichar c = [self characterAtIndex:index];
				NSInteger value = characterToBaseTwoValue(c);
				
				if (value == -1) {
					if (error != NULL) {
						NSString *desc = [NSString stringWithFormat:NSLocalizedString(@"'%c' is not a valid base 2 digit.", @"'%c' is not a valid base 2 digit."), c];
						NSString *recov = NSLocalizedString(@"You may only use 0 or 1 when entering a base two number.", @"You may only use 0 or 1 when entering a base two number.");
						NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:desc, NSLocalizedDescriptionKey, recov, NSLocalizedRecoverySuggestionErrorKey, nil];
						*error = [NSError errorWithDomain:kWCStringToNumberErrorDomain code:kWCInvalidBaseTwoDigit userInfo:dict];
						return NO;
					}
					return NO;
				}
				
				result += value * pow(2, exp);
			}
			
			if (value != NULL) {
				if (result == 0) {
					result = [currentValue unsignedIntegerValue];
				}
				
				*value = [NSNumber numberWithUnsignedInteger:result];
				return YES;
			}
			return NO;
	}
	return NO;
}

- (NSIndexPath *)indexPathFromString; {
	NSArray *indexObjects = [self componentsSeparatedByString:@","];
	NSUInteger index = 0, length = [indexObjects count];
	NSUInteger indexes[length];
	
	for (NSString *indexObject in indexObjects)
		indexes[index++] = [indexObject integerValue];
	
	return [NSIndexPath indexPathWithIndexes:indexes length:length];
}

+ (NSString *)replaceAllNewLineCharactersWithSymbolInString:(NSString *)string; {
	static NSString *darkSideLineEnding = nil;
	static NSString *macLineEnding = nil;
	static NSString *unixLineEnding = nil;
	static NSString *newLineSymbolString = nil;
	
	if (!unixLineEnding) {
		darkSideLineEnding = [[NSString alloc] initWithFormat:@"%C%C", 0x000D, 0x000A];
		macLineEnding = [[NSString alloc] initWithFormat:@"%C", 0x000D];
		unixLineEnding = [[NSString alloc] initWithFormat:@"%C", 0x000A];
		
		newLineSymbolString = [[NSString alloc] initWithFormat:@"%C", 0x23CE];
	}
	
	// To remove all newline characters in textString and replace it with a symbol, use NSMakeRange every time as the length changes
	NSMutableString *returnString = [NSMutableString stringWithString:string];
	
	[returnString replaceOccurrencesOfString:darkSideLineEnding withString:newLineSymbolString options:NSLiteralSearch range:NSMakeRange(0, [returnString length])];
	[returnString replaceOccurrencesOfString:macLineEnding withString:newLineSymbolString options:NSLiteralSearch range:NSMakeRange(0, [returnString length])];
	[returnString replaceOccurrencesOfString:unixLineEnding withString:newLineSymbolString options:NSLiteralSearch range:NSMakeRange(0, [returnString length])];
	
	return returnString;
}
#pragma mark *** Private Methods ***
- (WCNumberBase)baseForString:(NSInteger *)stop start:(NSInteger *)start {
	if ([self hasPrefix:@"0x"]) {
		*stop = 1;
		return WCBaseSixteen;
	}
	
	NSUInteger index, length = [self length];
	unichar firstChar = [self characterAtIndex:0];
	unichar lastChar = [self characterAtIndex:length - 1];
	
	if (firstChar == '$') {
		*stop = 0;
		return WCBaseSixteen;
	}
	else if (firstChar == '%') {
		*stop = 0;
		return WCBaseTwo;
	} 
	else if (lastChar == 'h') {
		*stop = -1;
		*start = [self length]-2;
		return WCBaseSixteen;
	}
	else if (lastChar == 'b') {
		*stop = -1;
		*start = [self length]-2;
		return WCBaseTwo;
	}
	
	for (index=0; index<length; index++) {
		unichar character = tolower([self characterAtIndex:index]);
		
		if (character == 'a') {
			*stop = -1;
			return WCBaseSixteen;
		}
		else if (character == 'b') {
			*stop = -1;
			return WCBaseSixteen;
		}
		else if (character == 'c') {
			*stop = -1;
			return WCBaseSixteen;
		}
		else if (character == 'd') {
			*stop = -1;
			return WCBaseSixteen;
		}
		else if (character == 'e') {
			*stop = -1;
			return WCBaseSixteen;
		}
		else if (character == 'f') {
			*stop = -1;
			return WCBaseSixteen;
		}
	}
	
	*stop = -1;
	return WCBaseTen;
}

static inline NSInteger characterToBaseTenValue (unichar c) {
	switch (c) {
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		default: return -1;
	}
}
static inline NSInteger characterToBaseSixteenValue (unichar c) {
	switch (c) {
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		case 'a': case 'A': return 10;
		case 'b': case 'B': return 11;
		case 'c': case 'C': return 12;
		case 'd': case 'D': return 13;
		case 'e': case 'E': return 14;
		case 'f': case 'F': return 15;
		default: return -1;
	}
}
static inline NSInteger characterToBaseTwoValue (unichar c) {
	switch (c) {
		case '0': return 0;
		case '1': return 1;
		default: return -1;
	}
}
@end
